package oop.ex2.commands;

import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import oop.ex2.exceptions.EmptyExpression;
import oop.ex2.exceptions.ExpectedAtomicExpression;
import oop.ex2.exceptions.ExpectedBinaryOperation;
import oop.ex2.exceptions.TerminatingStringNotFound;
import oop.ex2.exceptions.TokenNotRecognized;
import oop.ex2.exceptions.UnrecognizedVariable;
import oop.ex2.expression.ExpressionSolver;
import oop.ex2.expression.ExpressionTree;
import oop.ex2.tokenizer.CustomFunctionAnalyzer;
import oop.ex2.tokenizer.TokenAnalyzer;
import oop.ex2.tokenizer.VariableAnalyzer;

/**
 * This implements inline function declaration commands. It expects the command to be of the form:
 * inline _FUNC{@v1, ... } expression
 * 
 * It parses an expression and checks if it matches the format. If so, then it create a custom
 * function and adds it to the given expression solver.
 * @author daniel_i_l
 *
 */
public class InlineFunctionCommand extends Command {
	private static final String COMMAND_PATTERN = "\\s*inline";
    private static final String INLINE_FUNCTION_PATTERN = "\\s*\\s_[A-Z]+";
    private static final String ARGUMENTS_PATTERN = "\\{(.*?)\\}\\s";
    private static final String COMMA = ",";
    private static final int DEFAULT_VALUE = 0;

	private static Pattern _commandPattern;
	private static Pattern _inlineFunctionPattern;
    private static Pattern _argumentsPattern;

    /**
     * Construct an inline command object using a specific expression solver. This is the expression
     * solver that will be updated whenever a new function is added.
     * @param expressionSolver The expression solver to use.
     */
	public InlineFunctionCommand(ExpressionSolver expressionSolver) {
		super(expressionSolver);
        _commandPattern = Pattern.compile(COMMAND_PATTERN);
		_inlineFunctionPattern = Pattern.compile(INLINE_FUNCTION_PATTERN);
        _argumentsPattern = Pattern.compile(ARGUMENTS_PATTERN);
	}

	@Override
	public boolean executeCommand(String command) throws TokenNotRecognized,
			EmptyExpression, ExpectedAtomicExpression, ExpectedBinaryOperation,
			TerminatingStringNotFound, UnrecognizedVariable {

		//look for 'inline'
		Matcher commandMatcher = _commandPattern.matcher(command);
		if (!commandMatcher.lookingAt())
			return false;

		command = command.substring(commandMatcher.end());

		//check for new function name
		Matcher functionMatcher = _inlineFunctionPattern.matcher(command);
		if (!functionMatcher.lookingAt())
			return false;
		String functionName = functionMatcher.group().trim();
		
		command = command.substring(functionMatcher.end());

		//get the arguments
		Matcher argumentsMatcher = _argumentsPattern.matcher(command);
		if (!argumentsMatcher.lookingAt())
			return false;
        String arguments = argumentsMatcher.group(1).trim();
        ArrayList<String> argumentsList = new ArrayList<String>();
        initializeArguments(arguments, argumentsList);
        
        command = command.substring(argumentsMatcher.end()).trim();

        
        //compile the expression
        //first add the arguments to the expression solver so that it will recognize them
        for (String arg : argumentsList)
        	_expressionSolver.setVarValue(arg, DEFAULT_VALUE);
        
        //get the expression tree
        ExpressionTree functionCalculator = _expressionSolver.buildNewTree(command);
        
        CustomFunctionAnalyzer analyzer = new CustomFunctionAnalyzer(functionName, argumentsList, 
        		functionCalculator, _expressionSolver);

        _expressionSolver.removeFunctionAnalyzer(functionName); //remove existing analyzers
        _expressionSolver.addFunctionAnalyzer(analyzer);
        
		return true;

	}

    private boolean initializeArguments(String arguments, ArrayList<String> argumentsList)
    {   
        if (arguments.length() == 0)
        	return true;
        
        //search for first argument
        Pattern argPattern = Pattern.compile(TokenAnalyzer.WHITESPACE + VariableAnalyzer.VARIABLE_PATTERN);
        Matcher argumentMatcher = argPattern.matcher(arguments);
        
        if (!argumentMatcher.lookingAt()) //not a legal argument
        	return false;
        
        argumentsList.add(argumentMatcher.group().trim());
        arguments = arguments.substring(argumentMatcher.end());
        
        Pattern commaArgPattern = Pattern.compile(TokenAnalyzer.WHITESPACE + COMMA + 
        		TokenAnalyzer.WHITESPACE + "(" + VariableAnalyzer.VARIABLE_PATTERN + ")");
        
        while (arguments.length() != 0)
        {
        	argumentMatcher = commaArgPattern.matcher(arguments);
        	if (!argumentMatcher.lookingAt()) //not a legal argument
            	return false;
        	
        	//add this argument
        	argumentsList.add(argumentMatcher.group(1).trim());
            arguments = arguments.substring(argumentMatcher.end());
        }

        return true;
    }
}